如果要為APP設計一個共用的輸入框組件會需要有哪些功能和要注意的地方呢?
根據我常用的APP所設計的輸入框,我總結了幾個基本的需求:
RN內建的TextInput的樣式特點:沒有樣式(x)
placeholderTextColor
要給定邊框和底色才看的見輸入框:
(上面那炷香真的是輸入框沒錯...)
<TextInput
style={{
backgroundColor: 'white',
borderWidth: 1,
borderRadius: 4,
borderColor: '#cbcbcb'
}}
placeholderTextColor="#676767"
/>
給定寬度後不管內容多長輸入框都會維持住固定的寬度:
<TextInput
style={{
width: 250,
height: 40,
backgroundColor: 'white',
borderWidth: 1,
borderRadius: 4,
borderColor: '#cbcbcb'
}}
placeholderTextColor="#676767"
/>
補充:
- 如果要實現錯誤時顯示紅色邊框也很簡單,直接動態調整邊框顏色即可
borderColor: !!error ? '#ff0000' : '#cbcbcb'
- 只想要底線則可以將
borderWidth
改為borderBottomWidth
TextInput 有一個 clearButtonMode
屬性(僅限iOS)用於顯示文字清除按鈕,點擊後可以清除輸入框文字。
缺點:
<TextInput
style={{
width: 250,
height: 40,
backgroundColor: 'white',
borderWidth: 1,
borderRadius: 4,
borderColor: '#cbcbcb'
}}
placeholderTextColor="#676767"
clearButtonMode="always"
/>
Android 沒有類似的屬性,因此為了各系統icon保持一致的樣式,通常會選擇自己刻一個而不是使用 clearButtonMode
。
這邊以搜索輸入框和密碼輸入框為例,常見的搜索框左側會有放大鏡圖示,而密碼輸入框右側會有顯示/隱藏按鈕:
因為 TextInput 本身是沒有提供 icon 的屬性,所以如果要實現這種效果需要另外封裝 TextInput,藉由傳入的 leftIcon
, rightIcon
屬性決定在輸入框左右側顯示什麼樣的icon。
import React from 'react'
import { StyleSheet, View, TextInput, type TextInputProps } from 'react-native'
interface InputProps extends TextInputProps {
leftIcon?: JSX.Element
rightIcon?: JSX.Element
}
export const Input = ({ style, leftIcon, rightIcon, ...restProps }: InputProps) => {
return (
<View style={[styles.inputContainer, style]}>
{!!leftIcon && leftIcon}
<TextInput
style={styles.input}
placeholderTextColor="#676767"
{...restProps}
/>
{!!rightIcon && rightIcon}
</View>
)
}
const styles = StyleSheet.create({
inputContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
width: 250,
height: 40,
backgroundColor: 'white',
borderWidth: 1,
borderRadius: 4
},
input: {
flex: 1,
paddingHorizontal: 8
}
})
使用方式:
<Input leftIcon={<Icon name="search" size={24} color="#cbcbcb" />} />
<Input
rightIcon={
<TouchableOpacity onPress={() => setVisible(prev => !prev)}>
<Icon name={visible ? "visibility" : "visibility-off"} size={24} color="#cbcbcb" />
</TouchableOpacity>
}
secureTextEntry={!visible}
/>
注意:
- 因為 TextInput 本身是沒有樣式,而 TextInput 加上左右側 icon 後才能算是一個完整的輸入框,所以邊框、寬高都要設置在最外層的 View 而不是 TextInput
- 建議將基本的 Input 和帶有 icon 的 Input 拆分成不同的組件,以避免組件在不同情境中使用產生的副作用。(SOLID 中的 Open-Closed Principle)
TextInput 有兩個關於鍵盤類型的屬性:inputMode
, keyboardType
,這兩個屬性用於決定鍵盤上面顯示的內容(例如設為 numeric 就是純數字鍵盤)
'decimal', 'email', 'none', 'numeric', 'search', 'tel', 'text', 'url'
'default', 'email-address', 'numeric', 'phone-pad', 'ascii-capable', 'numbers-and-punctuation', 'url', 'number-pad', 'name-phone-pad', 'decimal-pad', 'twitter', 'web-search', 'visible-password'
<TextInput inputMode="numeric" />
<TextInput inputMode="decimal" />
<TextInput keyboardType="phone-pad" />
keyboardType 和 inputMode 基本上沒有什麼區別,只不過 keyboardType 比 inputMode 多了幾個僅Android和僅iOS的類型,實作上我個人更偏向使用 keyboardType,不過兩個都可以滿足基本需求。
下圖為 keyboardType 各值所顯示的鍵盤內容:
圖片來源於:https://lefkowitz.me/visual-guide-to-react-native-textinput-keyboardtype-options/
注意事項:
<TextInput keyboardType="phone-pad" inputMode="email" />
出現的還是 Email 鍵盤。因為 TextInput 本身並沒有樣式,所以要實作 TextArea 也滿簡單的,只需要調整樣式...而已嗎?
其實不是。TextInput 預設只能輸入一行內容,所以不管你輸入多長的內容它都會一直往後顯示,不會換行也不能換行,因此除了樣式之外,還需要設置 TextInput 中的 multiline
屬性來允許換行輸入。
TextInput 還有一個 numberOfLines
屬性用於設置輸入框預設顯示幾列高度,但這個屬性僅限 Android 使用,在 iOS 無效。
<TextInput
style={{
width: 220,
paddingVertical: 8,
paddingHorizontal: 12
}}
placeholderTextColor="#676767"
multiline={true}
numberOfLines={4}
/>
注意事項:
numberOfLines={4}
後輸入框預設高度變成四列了,但因為沒有設置高度的關係,當內容超過四列時輸入框就會自動延伸,最後變成巨無霸(x)textAlignVertical="top"
才會從最上方開始顯示。<TextInput
style={{
width: 220,
height: 100, // 不給 height 的話輸入框會無限延長
paddingVertical: 8,
paddingHorizontal: 12
}}
placeholderTextColor="#676767"
multiline={true}
numberOfLines={4}
textAlignVertical="top"
/>
iOS 沒有 numberOfLines
屬性,但只需要自行設置輸入框高度就行:
<TextInput
style={{
width: 220,
height: 100,
paddingVertical: 8,
paddingHorizontal: 10
}}
placeholderTextColor="#676767"
multiline={true}
/>
有時候會遇到給 TextArea 加上 icon 的需求,比如像是 chatGPT 的輸入框右下角會有一個送出的 icon,不管輸入多少行內容它永遠固定在輸入框的右下方:
實現方式也很簡單,就跟封裝單行輸入框是一樣的方式,只不過右側 icon 的樣式需要加上 position: 'absolute'
和 right
, bottom
,將 icon 固定在右下角:
export const TextArea = ({ style, height = 100, icon, ...restProps }: TextAreaProps) => {
return (
<View style={styles.container}>
<TextInput
style={[styles.input, style, { height }]}
placeholderTextColor="#676767"
multiline={true}
numberOfLines={4}
textAlignVertical="top"
{...restProps}
/>
{!!icon && (
<Icon name={icon} style={styles.icon} onPress={//...} />
)}
</View>
)
}
const styles = StyleSheet.create({
container: {
width: 250,
borderWidth: 1,
backgroundColor: 'white',
marginVertical: 5,
borderRadius: 4,
},
input: {
width: 220,
paddingVertical: 8,
paddingHorizontal: Platform.OS === 'android' ? 12 : 10
},
icon: {
position: 'absolute',
right: 6,
bottom: 10
}
})